﻿#include "VmdFile.h"

VmdFile::VmdFile(QWidget *parent)
	: QWidget(parent)
{
	readBoneTranslatorFile();

	// These are default VMD interpolation values
	// I'm not sure how they are used, took these values from a VMD file
	// They make the difference between the file working and not working
	VectorXf vx,vy,vz,vr;
	vx = vy = vz = vr = VectorXf::Zero(16);
	vx << 20,20,20,20,20,20,20,20,107,107,107,107,107,107,107,107;
	vy << 20,20,20,20,20,20,20,107,107,107,107,107,107,107,107,1;
	vz << 20,20,20,20,20,20,107,107,107,107,107,107,107,107,1,0;
	vr << 20,20,20,20,20,107,107,107,107,107,107,107,107,1,0,0;

	for (int n=0;n<16;n++) {
		m_intX[n] = vx(n);
		m_intY[n] = vy(n);
		m_intZ[n] = vz(n);
		m_intRot[n] = vr(n);
	}
}

VmdFile::~VmdFile()
{

}

void VmdFile::readBoneTranslatorFile()
{
	QFile file("bonemap.txt");
	QFile file2("bonemap2.txt");
	if (file.open(QIODevice::ReadOnly)) {
		QTextStream buffer(&file);
		buffer.setCodec("UTF-8");

		file2.open(QIODevice::WriteOnly | QIODevice::Text);
		QTextStream stream(&file2);
		stream.setCodec("UTF-8");

		int count = 0;
		while (!buffer.atEnd()) {
			count++;
			QString text = buffer.readLine();
			//qDebug() << text.count() << text;
			text.remove("(");
			text.remove(")");
			text.remove("\"");
			text.remove("\"");
			text.remove(" ");
			if (text.contains(",")) {
				QStringList parselist = text.split(",");
				m_eBoneList.append(parselist.value(0));
				m_jBoneList.append(parselist.value(1));
			}
			if (count < 6) {

			}
			
		}
		file.close();
		file2.close();
	}
}

void VmdFile::load(QString path)
{
	QFile file(path);
	if (!file.exists()) return;
	if (file.open(QIODevice::ReadOnly)) {
		QDataStream buffer(&file);
		buffer.setByteOrder(QDataStream::LittleEndian);

		qDebug() << "Opened VMD File";

		char	szHeader[30];			// "Vocaloid Motion Data 0002"
		char	szModelName[20];		// 対象モデル名

		buffer.readRawData(szHeader,30);
		buffer.readRawData(szModelName,20);

		//QString header, modelName;
		for (int n=0;n<30;n++) m_header.append(szHeader[n]);
		for (int n=0;n<20;n++) m_modelName.append(szModelName[n]);

		qDebug() << "Header" << m_header;
		qDebug() << "Model Name" << m_modelName;

		unsigned long	ulNumBoneKeyFrames;
		buffer.readRawData((char*)&ulNumBoneKeyFrames,sizeof(unsigned long));
		qDebug() << "NumBoneKeyFrames" << ulNumBoneKeyFrames;
		m_keyFramesToWrite = ulNumBoneKeyFrames;

		QStringList boneNameList;
		QList<QByteArray> arrayList;

		QList<VMD_Motion> motionList;
		for (int n=0;n<ulNumBoneKeyFrames;n++) {
			VMD_Motion motionVal;
			// size needs to be 111 because compiler rounds sizeof(VMD_Motion) up to 112 for struct alignment
			// the only way to fix would be to reduce struct alignment to 1 but I'm pretty sure
			// Qt doesn't work at alignments less than 4
			buffer.readRawData((char*)&motionVal.szBoneName,15);
			buffer.readRawData((char*)&motionVal.ulFrameNo,111-15);
			motionList.append(motionVal);
			QString boneName = QString(motionVal.szBoneName);
			if (!boneNameList.contains(boneName)) {
				boneNameList.append(boneName);
				arrayList.append(nameToArray(motionVal.szBoneName));
			}
		}

		QVector<int> frameCount(boneNameList.count(),0);
		

		QList<int> boneCountList;

		for (int n=0;n<motionList.count() ;n++) {
			int length = 14;
			for (int d=0;d<15;d++) {
				if (motionList[n].szBoneName[d] == '\0') length = d+1;
				break;
			}
			QByteArray encodedString(motionList[n].szBoneName);
			QTextCodec *codec = QTextCodec::codecForName("Shift-JIS");
			QString jBoneName = codec->toUnicode(encodedString);
			QString eBoneName = jBone2eBone(jBoneName);
			if (eBoneName == "Bone not found") {
				qDebug() << "bone not found";
				continue;
			}
			if (m_boneList.contains(eBoneName)) {
				int index = m_boneList.indexOf(eBoneName);
				boneCountList[index]++;
				continue;
			}
			m_boneList.append(eBoneName);
			boneCountList.append(0);
		}

		for (int d=0;d<m_boneList.count();d++) {
			QList<VMD_BoneMotion> bmList;
			for (int n=0;n<motionList.count();n++) {
				QByteArray encodedString(motionList[n].szBoneName);
				QTextCodec *codec = QTextCodec::codecForName("Shift-JIS");
				QString jBoneName = codec->toUnicode(encodedString);
				QString eBoneName = jBone2eBone(jBoneName);
				if (eBoneName == m_boneList.value(d)) {
					VMD_BoneMotion bm;
					bm.name = m_boneList.value(d);
					bm.frame = motionList[n].ulFrameNo;
					bm.pos = Vector3f::Zero();
					bm.rot = Vector4f::Zero();
					bm.pos << motionList[n].vec3Position[0],
						      motionList[n].vec3Position[1],
							  motionList[n].vec3Position[2];
					bm.rot << motionList[n].vec4Rotation[0],
						      motionList[n].vec4Rotation[1],
						      motionList[n].vec4Rotation[2],
						      motionList[n].vec4Rotation[3];
					bmList.append(bm);
				}
			}
			m_motionMap[m_boneList.value(d)] = bmList;
		}

		file.close();
	}
}

void VmdFile::save(QString path)
{
	QFile file(path);
	if (file.open(QIODevice::WriteOnly)) {
		QDataStream buffer(&file);
		buffer.setByteOrder(QDataStream::LittleEndian);

		m_header = "Vocaloid Motion Data 0002";
		QByteArray header = m_header.toAscii();
		while (header.length() < 30) header.append((char)0x0);
		QByteArray modelName;
		// I think this model name indicates that the motion is for Miku
		// I couldn't figure out exactly how to write it since it's not in english
		// These hex values are taken from another VMD file
		// If a different model is used in MikuMikuDance an error message is given
		//   but it works fine
		modelName.append(0x8f);
		modelName.append(0x89);
		modelName.append(0x89);
		modelName.append(0xb9);
		modelName.append(0x83);
		modelName.append(0x7e);
		modelName.append(0x83);
		modelName.append(0x4e);
		modelName.append((char)0x0);

		while (modelName.length() < 20) modelName.append(0xfd);
		qDebug() << modelName.length() << "modelName" << modelName;

		buffer.writeRawData((char*)header.data(),30);
		buffer.writeRawData((char*)modelName.data(),20);

		unsigned long ulNumBoneKeyFrames = m_keyFramesToWrite;
		buffer.writeRawData((char*)&ulNumBoneKeyFrames,sizeof(unsigned long));

		QList<QString> keys = m_motionMap.keys();
		for (int d=0;d<keys.count();d++) {
			for (int n=0;n<m_motionMap[keys.value(d)].count();n++) {
				VMD_Motion motionVal;
				QString jBoneName = eBone2jBone(keys.value(d));
		//->		// should figure out how to assign jBoneName to motionVal.szBoneName
				motionVal.ulFrameNo = n;
				motionVal.vec3Position[0] = m_motionMap[keys.value(d)][n].pos(0);
				motionVal.vec3Position[1] = m_motionMap[keys.value(d)][n].pos(1);
				motionVal.vec3Position[2] = m_motionMap[keys.value(d)][n].pos(2);
				motionVal.vec4Rotation[0] = m_motionMap[keys.value(d)][n].rot(0);
				motionVal.vec4Rotation[1] = m_motionMap[keys.value(d)][n].rot(1);
				motionVal.vec4Rotation[2] = m_motionMap[keys.value(d)][n].rot(2);
				motionVal.vec4Rotation[3] = m_motionMap[keys.value(d)][n].rot(3);
				for (int k=0;k<16;k++) {
					motionVal.cInterpolationX[k] = m_intX[k];
					motionVal.cInterpolationY[k] = m_intY[k];
					motionVal.cInterpolationZ[k] = m_intZ[k];
					motionVal.cInterpolationRot[k] = m_intRot[k];
				}
				QTextCodec *codec = QTextCodec::codecForName("Shift-JIS");
				QByteArray encodedString = codec->fromUnicode(jBoneName);
				encodedString.append('\0');
				while (encodedString.length() < 15) encodedString.append(0xfd);
				buffer.writeRawData((char*)encodedString.data(),15);
				buffer.writeRawData((char*)&motionVal.ulFrameNo,111-15);
			}
		}

		file.close();
	}
	QMessageBox::information(0,"Info","File saved");
}

QString VmdFile::eBone2jBone(QString text)
{
	if (m_eBoneList.contains(text)) {
		int index = m_eBoneList.indexOf(text);
		return m_jBoneList.value(index);
	} else return "Bone not found";
}

QString VmdFile::jBone2eBone(QString text)
{
	if (m_jBoneList.contains(text)) {
		int index = m_jBoneList.indexOf(text);
		return m_eBoneList.value(index);
	} else return "Bone not found";
}

QByteArray VmdFile::nameToArray(char* str)
{
	QByteArray byteArray;
	for (int n=0;n<16;n++) {
		if (str[n] == '\0') break;
		byteArray.append((quint8)str[n]);
	}
	return byteArray;
}

QString VmdFile::arrayToString(QByteArray byteArray)
{
	QString text;
	for (int n=0;n<byteArray.count();n++) {
		text.append(QString::number((quint8)byteArray.at(n),16));
	}
	return text;
}

QString VmdFile::printInterpolationValues(VMD_Motion inp)
{
	QStringList x,y,z,r;
	for (int n=0;n<16;n++) {
		x.append(QString::number((int)inp.cInterpolationX[n]));
		y.append(QString::number((int)inp.cInterpolationY[n]));
		z.append(QString::number((int)inp.cInterpolationZ[n]));
		r.append(QString::number((int)inp.cInterpolationRot[n]));
	}
	QString str = x.join(",") + " " + y.join(",") + " " + z.join(",") + " " + r.join(",");
	return str;
}